#include "TiffVolume.h"
#include "GDALCPP.h"
#include "OCMain/Log.h"
#include "OCMain/DataStream.h"
#include "json/json.h"
#include "OCVolume/VolumeGridData.h"
#include "OCVolume/VolumeUtil.h"
#include "OCVolume/VolumeAlgorithm.h"

namespace OCPP
{
	using namespace Volume;

	bool gdalInitialized1 = false;
	TiffVolume::TiffVolume()
	{
		if (!gdalInitialized1)
		{
			gdalInitialized1 = true;
			OGRRegisterAll();
		}
	}

	TiffVolume::~TiffVolume()
	{

	}

	StringMap readMeta(GDALDataset* dataSet, GDALRasterBand* band)
	{
		StringMap  meta;
		CSLConstList names1 = band->GetMetadataDomainList();
		for (int i = 0; names1 && names1[i]; i++)
		{
			String domain = names1[i];
			CSLConstList values = band->GetMetadata(domain.c_str());
			for (int j = 0; values && values[j]; j++)
			{
				String val = values[j];
				StringVector sv = StringUtil::split(val, "=");
				if (sv.size() == 2)
				{
					String k = sv[0];
					String v = sv[1];
					StringUtil::trim(k);
					StringUtil::trim(v);
					meta[k] = v;
				}
				else
				{
					meta[val] = val;
				}
			}
		}
		return meta;
	};

	MemoryDataStreamPtr getData(GDALDataset* dataSet, int dimX, int dimY, GDALDataType dataType)
	{
		long size = dimX * dimY;

		if (dataType == GDT_Int16)
		{
			MemoryDataStreamPtr dataStream(new MemoryDataStream(new short[size], size, true));
			GDALRasterBand* band = dataSet->GetRasterBand(1);
			band->RasterIO(GF_Read, 0, 0, dimX, dimY, dataStream->getPtr(), dimX, dimY, GDT_Int16, 0, 0);
			return dataStream;
		}
		else
		{
			MemoryDataStreamPtr dataStream(new MemoryDataStream(new int[size], size, true));
			GDALRasterBand* band = dataSet->GetRasterBand(1);
			band->RasterIO(GF_Read, 0, 0, dimX, dimY, dataStream->getPtr(), dimX, dimY, GDT_Int32, 0, 0);
			return dataStream;
		}
	}

	MemoryDataStreamPtr getData(String fileName, int dimX, int dimY, GDALDataType dataType)
	{
		GDALDataset* dataSet = (GDALDataset*)GDALOpen(fileName.c_str(), GA_ReadOnly);
		return getData(dataSet, dimX, dimY, dataType);
	}

	inline float getFloat(MemoryDataStreamPtr dataStream, long offset, GDALDataType dataType)
	{
		if (dataType == GDT_Int16)
		{
			return ((short*)dataStream->getPtr())[offset];
		}
		else
		{
			return ((int*)dataStream->getPtr())[offset];
		}
	}

	FloatVector getFloatData(String fileName, int dimX, int dimY, GDALDataType dataType, float scale)
	{
		FloatVector data;
		data.resize(dimX * dimY);
		GDALDataset* dataSet = (GDALDataset*)GDALOpen(fileName.c_str(), GA_ReadOnly);
		MemoryDataStreamPtr dataStream = getData(dataSet, dimX, dimY, dataType);
		for (long y = 0, offset = 0; y < dimY; y++)
		{
			int flipY = dimY - y - 1;
			for (long x = 0; x < dimX; x++, offset++)
			{
				int i0 = y * dimX + x;
				int i1 = flipY * dimX + x;
				data[i0] = getFloat(dataStream, i1, dataType) / scale;
			}
		}

		return data;
	}

	std::tuple<VolumeExtent, GDALDataType> getDim(String fileName)
	{
		GDALDataset* dataSet = (GDALDataset*)GDALOpen(fileName.c_str(), GA_ReadOnly);
		assert(dataSet != NULL);
		int dimX = GDALGetRasterXSize(dataSet);
		int dimY = GDALGetRasterYSize(dataSet);
		GDALRasterBand* band = dataSet->GetRasterBand(1);
		auto dataType = band->GetRasterDataType();
		double adfGeoTransform[6];
		dataSet->GetGeoTransform(&adfGeoTransform[0]);
		Vector2 origin(adfGeoTransform[0], adfGeoTransform[3]);
		Vector2 pixelOffset(adfGeoTransform[1], adfGeoTransform[5]);
		Vector2 endFrame;
		endFrame.x = origin.x + pixelOffset.x * dimX;
		endFrame.y = origin.y + pixelOffset.y * dimY;
		VolumeExtent gridExtent;
		gridExtent.dimX = dimX;
		gridExtent.dimY = dimY;
		gridExtent.xMin = origin.x;
		gridExtent.yMin = endFrame.y;
		gridExtent.xMax = endFrame.x;
		gridExtent.yMax = origin.y;
		gridExtent.zMin = 0.0;
		gridExtent.zMax = 20000.0;
		GDALClose(dataSet);
		return std::make_tuple(gridExtent, dataType);
	}

	IntVector levels = {
		200,250,300,350,400,450,500,550,600,650,700,750,800,850,900,950,1000
	};

	void TiffVolume::readOne(String path, String fileName, VolumeOptions& options)
	{
		String uFileName = joinPath(path, fileName);
		auto [extent, dataType] = getDim(uFileName);
		int dimX = extent.dimX;
		int dimY = extent.dimY;
		int dimZ = (int)levels.size();
		extent.dimZ = dimZ;

		VolumeGridData gridDataU;
		gridDataU.setType(VolumeGridData::Type_Float);
		VolumeExtent& gridExtent = gridDataU.getExtent();
		gridExtent = extent;

		int dimXY = dimX * dimY;
		gridDataU._vecFloat.resize(dimX * dimY * dimZ);

		for (int z = 0; z < dimZ; z++)
		{
			String levelString = str_format("_%d_", levels[dimZ - z - 1] * 100);
			String uFileNameLevel = StringUtil::replaceAll(uFileName, "_0_", levelString);
			auto dataU = getFloatData(uFileNameLevel, dimX, dimY, dataType, 10.0);
			memcpy(&gridDataU._vecFloat[dimXY * z], &dataU[0], dimXY * sizeof(float));
		}

		gridDataU.ClipIf(options);
		gridDataU.addDimension(fileName);
		options.getWriter().write(gridDataU, options);
	}

	void TiffVolume::readUV(String path, String fileName, VolumeOptions& options)
	{
		String uName = "WIU";
		String vName = "WIV";
		String uFile = fileName;
		String vFile = StringUtil::replaceAll(fileName, uName, vName);
		String uFileName = joinPath(path, uName, uFile);
		String vFileName = joinPath(path, vName, vFile);
		auto [extent, dataType] = getDim(uFileName);
		int dimX = extent.dimX;
		int dimY = extent.dimY;
		auto dataU = getFloatData(uFileName, dimX, dimY, dataType, 10.0);
		auto dataV = getFloatData(vFileName, dimX, dimY, dataType, 10.0);

		VolumeGridData gridDataU;
		gridDataU.setType(VolumeGridData::Type_Float);
		VolumeExtent& gridExtent = gridDataU.getExtent();
		gridExtent = extent;

		VolumeGridData gridDataV = gridDataU;

		gridDataU.addDimension(uFile);
		gridDataV.addDimension(vFile);
		gridDataU._vecFloat = std::move(dataU);
		gridDataV._vecFloat = std::move(dataV);
		gridDataU.ClipIf(options);
		gridDataV.ClipIf(options);

		NcGridDataList list;
		list.push_back(gridDataU);
		list.push_back(gridDataV);

		NcAlgorithmUV algorithm;
		VolumeGridData gridData = algorithm.onCompute(list, options);
		options.getWriter().write(gridData, options);
	}

	void TiffVolume::readUV2(String path, String fileName, VolumeOptions& options)
	{
		int dimZ = (int)levels.size();

		String uName = "WIU";
		String vName = "WIV";
		String uFile = fileName;
		String vFile = StringUtil::replaceAll(fileName, uName, vName);
		String uFileName = joinPath(path, uName, uFile);
		String vFileName = joinPath(path, vName, vFile);

		auto [extent, dataType] = getDim(uFileName);
		int dimX = extent.dimX;
		int dimY = extent.dimY;
		extent.dimZ = dimZ;


		VolumeGridData gridDataU;
		gridDataU.setType(VolumeGridData::Type_Float);
		VolumeExtent& gridExtent = gridDataU.getExtent();
		gridExtent = extent;

		VolumeGridData gridDataV = gridDataU;
		gridDataU.addDimension(uFile);
		gridDataV.addDimension(vFile);

		int dimXY = dimX * dimY;
		gridDataU._vecFloat.resize(dimX * dimY * dimZ);
		gridDataV._vecFloat.resize(dimX * dimY * dimZ);

		for (int z = 0; z < dimZ; z++)
		{
			String levelString = str_format("_%d_", levels[dimZ - z - 1] * 100);
			String uFileNameLevel = StringUtil::replaceAll(uFileName, "_0_", levelString);
			String vFileNameLevel = StringUtil::replaceAll(vFileName, "_0_", levelString);
			auto dataU = getFloatData(uFileNameLevel, dimX, dimY, dataType, 10.0);
			auto dataV = getFloatData(vFileNameLevel, dimX, dimY, dataType, 10.0);
			memcpy(&gridDataU._vecFloat[dimXY * z], &dataU[0], dimXY * sizeof(float));
			memcpy(&gridDataV._vecFloat[dimXY * z], &dataV[0], dimXY * sizeof(float));
		}

		gridDataU.ClipIf(options);
		gridDataV.ClipIf(options);

		NcGridDataList list;
		list.push_back(gridDataU);
		list.push_back(gridDataV);

		NcAlgorithmVecLength algorithm;
		VolumeGridData gridData = algorithm.onCompute(list,options);
		options.getWriter().write(gridData, options);
	}

	void TiffVolume::read(String path, String fileName, VolumeOptions& options)
	{
		String tempName = StringUtil::replaceAll(fileName, "{s}","0");
		GDALDataset* dataSet = (GDALDataset*)GDALOpen(joinPath(path, tempName).c_str(), GA_ReadOnly);
		if (dataSet == NULL)
		{
			return;
		}
		int bandCount = dataSet->GetRasterCount();
		if (bandCount != 1)
		{
			GDALClose(dataSet);
			return;
		}

		double adfGeoTransform[6];
		dataSet->GetGeoTransform(&adfGeoTransform[0]);

		int dimX = GDALGetRasterXSize(dataSet);
		int dimY = GDALGetRasterYSize(dataSet);

		Vector2 origin(adfGeoTransform[0], adfGeoTransform[3]);
		Vector2 pixelOffset(adfGeoTransform[1], adfGeoTransform[5]);

		Vector2 endFrame;
		endFrame.x = origin.x + pixelOffset.x * dimX;
		endFrame.y = origin.y + pixelOffset.y * dimY;

		Json::Value json;
		String extent = Stringify() << origin.x << ","
			<< endFrame.y << ","
			<< endFrame.x << ","
			<< origin.y;
		json["Extent"] = extent;

		GDALRasterBand* band = dataSet->GetRasterBand(1);
		StringMap meta1 = readMeta(dataSet, band);
		for (auto it : meta1)
		{
			json[it.first] = it.second;
		}

		int blockSizeX, blockSizeY;
		band->GetBlockSize(&blockSizeX, &blockSizeY);
		int nXBlocks = (dimX + blockSizeX - 1) / blockSizeX;
		int nYBlocks = (dimY + blockSizeY - 1) / blockSizeY;

		String datatype = GDALGetDataTypeName(band->GetRasterDataType());
		json["Data type"] = datatype;
		double noDataVal = band->GetNoDataValue();

		long size = dimX * dimY;
		MemoryDataStreamPtr dataStream = getData(dataSet, dimX, dimY, band->GetRasterDataType());

		std::vector<MemoryDataStreamPtr> floorData;
		floorData.push_back(dataStream);
		for (int i = 1; i < 24; i++)
		{
			String tempName = StringUtil::replaceAll(fileName, "{s}", std::to_string(i));
			floorData.push_back(getData(joinPath(path,tempName), dimX, dimY, band->GetRasterDataType()));
		}

		float minValue = 1e+5;
		float maxValue = -minValue;
		float scale = 10.0f;
		if (!json["scale"].isNull())
		{
			scale = std::atoi(json["scale"].asCString());
		}
		int dimZ = (int)floorData.size();
		VolumeGridData gridData;
		gridData.setType(VolumeGridData::Type_Float);
		FloatVector& data = gridData._vecFloat;
		data.resize(size * dimZ);

		for (long z = 0; z < dimZ; z++)
		{
			long zOffset = z * size;
			for (long y = 0, offset = 0; y < dimY; y++)
			{
				for (long x = 0; x < dimX; x++, offset++)
				{
					float val = ((int*)floorData[z]->getPtr())[offset];
					if (Math::floatEqual(noDataVal, val))continue;
					val /= scale;
					if (val < minValue) minValue = val;
					if (val > maxValue) maxValue = val;
					data[zOffset + offset] = val;
				}
			}
		}
		json["Min value"] = minValue;
		json["Max value"] = maxValue;
		OCPP_LOG_INFO << json.toStyledString() << "\n";

		VolumeExtent& gridExtent = gridData.getExtent();
		gridExtent.dimX = dimX;
		gridExtent.dimY = dimY;
		gridExtent.dimZ = dimZ;
		gridExtent.xMin = origin.x;
		gridExtent.yMin = endFrame.y;
		gridExtent.xMax = endFrame.x;
		gridExtent.yMax = origin.y;
		gridExtent.zMin = 0.0;
		gridExtent.zMax = 20000.0;

		gridData._dimensions.push_back(fileName);
		gridData.reverseY();
		gridData.ClipIf(options);

		options.getWriter().write(gridData, options);
		GDALClose(dataSet);
	}
}